package net.pimpas.locking.entity.serviceprovider;

import java.io.Serializable;
import java.util.Iterator;

import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.locking.serviceprovider.LockingServiceProviderImpl;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.persistence.util.PersistenceUtil;
import net.pimpas.util.reflection.ReflectionUtil;

@ServiceProviderAnnotation(
		instanceType=ServiceProviderInstanceType.SINGLETON,
		serviceProviderType=EntityLockingServiceProvider.class)
public class EntityLockingServiceProviderImpl extends LockingServiceProviderImpl 
implements EntityLockingServiceProvider {
	
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(EntityLockingServiceProviderImpl.class);
	
	private Object findLockEntry( Object object ) {
		
		Object value = PersistenceUtil.getEntityIdValue(object);
		
		if(value == null)
			throw new NullPointerException("Cannot lock entity with NULL in @Id value");
		
		synchronized(locks) {
			Iterator<Object> it = locks.keySet().iterator();
			EntityObject tmp = null;
			Object obj = null;
			while(it.hasNext()) {
				// check, entitylock?
				obj = it.next();
				if(!ReflectionUtil.isSubclass(obj.getClass(), EntityObject.class))
					// just continue looking for entityObject
					continue;
				tmp = (EntityObject) obj;
				if(tmp.getIdentifier() != null && tmp.getIdentifier().equals(value) && 
						tmp.getEntityClass().equals(object.getClass()))
					return tmp;
			}
		}
		
		return null;
	}
	
	private EntityObject createEntityObject( Object entity ) {
		Object value = PersistenceUtil.getEntityIdValue(entity);
		
		if(value == null)
			throw new NullPointerException("Cannot lock entity with NULL in @Id value");
		
		return new EntityObject(value, entity.getClass());
		
	}
	
	public void lockRead(Object entity) {
		Object key = this.findLockEntry(entity);
		if(key == null) {
			entity = this.createEntityObject(entity);
			super.createLock(entity);
		}
		else
			entity = key;
		
		super.lockRead(entity);
	}

	public void lockWrite(Object entity) {
		Object key = this.findLockEntry(entity);
		if(key == null) {
			entity = this.createEntityObject(entity);
			super.createLock(entity);
		}
		else
			entity = key;
		
		super.lockWrite(entity);
	}

	public void unlockRead(Object entity) {
		Object key = this.findLockEntry(entity);
		if(key == null) return;
		
		super.unlockRead(key);
	}

	public void unlockWrite(Object entity) {
		Object key = this.findLockEntry(entity);
		if(key == null) return;
		
		super.unlockWrite(key);
	}

	public void config() throws ConfigurationException {
		logger.info("[EntityLockingProvider]: Initializing EntityLockingServiceProvider ...");
	}
	
	private static final class EntityObject implements Serializable {
		private static final long serialVersionUID = -1778997347066104256L;
		
		private Object identifier;
		private Class<?> entityClass;
		
		public EntityObject(Object identifier, Class<?> entityClass) {
			this.setIdentifier(identifier);
			this.setEntityClass(entityClass);
		}
		
		public Class<?> getEntityClass() {
			return entityClass;
		}
		public void setEntityClass(Class<?> entityClass) {
			this.entityClass = entityClass;
		}
		public Object getIdentifier() {
			return identifier;
		}
		public void setIdentifier(Object identifier) {
			this.identifier = identifier;
		}
		
		@Override
		public String toString() {
			return "{LockEntity," +
					"[Identifier: "+this.getIdentifier() +"]," + 
					"[EntityClass: "+this.getEntityClass() + "]" + 
					"}";
		}
	}
	
	public boolean isTerminated() {
		return false;
	}
}